/*
 * Copyright 2013, Edmodo, Inc. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License.
 * You may obtain a copy of the License in the LICENSE file, or at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" 
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
 */

package com.smartandroid.sa.cropper;

import android.content.Context;
import android.util.Pair;
import android.util.TypedValue;

/**
 * Utility class to perform basic operations with Handles.
 */
public class HandleUtil {

	// Private Constants ///////////////////////////////////////////////////////

	// The radius (in dp) of the touchable area around the handle. We are basing
	// this value off of the recommended 48dp Rhythm. See:
	// http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm
	private static final int TARGET_RADIUS_DP = 24;

	// Public Methods //////////////////////////////////////////////////////////

	/**
	 * Gets the default target radius (in pixels). This is the radius of the
	 * circular area that can be touched in order to activate the handle.
	 * 
	 * @param context
	 *            the Context
	 * @return the target radius (in pixels)
	 */
	public static float getTargetRadius(Context context) {

		final float targetRadius = TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, TARGET_RADIUS_DP, context
						.getResources().getDisplayMetrics());
		return targetRadius;
	}

	/**
	 * Determines which, if any, of the handles are pressed given the touch
	 * coordinates, the bounding box, and the touch radius.
	 * 
	 * @param x
	 *            the x-coordinate of the touch point
	 * @param y
	 *            the y-coordinate of the touch point
	 * @param left
	 *            the x-coordinate of the left bound
	 * @param top
	 *            the y-coordinate of the top bound
	 * @param right
	 *            the x-coordinate of the right bound
	 * @param bottom
	 *            the y-coordinate of the bottom bound
	 * @param targetRadius
	 *            the target radius in pixels
	 * @return the Handle that was pressed; null if no Handle was pressed
	 */
	public static Handle getPressedHandle(float x, float y, float left,
			float top, float right, float bottom, float targetRadius) {

		Handle pressedHandle = null;

		// Note: corner-handles take precedence, then side-handles, then center.

		if (HandleUtil.isInCornerTargetZone(x, y, left, top, targetRadius)) {
			pressedHandle = Handle.TOP_LEFT;
		} else if (HandleUtil.isInCornerTargetZone(x, y, right, top,
				targetRadius)) {
			pressedHandle = Handle.TOP_RIGHT;
		} else if (HandleUtil.isInCornerTargetZone(x, y, left, bottom,
				targetRadius)) {
			pressedHandle = Handle.BOTTOM_LEFT;
		} else if (HandleUtil.isInCornerTargetZone(x, y, right, bottom,
				targetRadius)) {
			pressedHandle = Handle.BOTTOM_RIGHT;
		} else if (HandleUtil.isInCenterTargetZone(x, y, left, top, right,
				bottom) && focusCenter()) {
			pressedHandle = Handle.CENTER;
		} else if (HandleUtil.isInHorizontalTargetZone(x, y, left, right, top,
				targetRadius)) {
			pressedHandle = Handle.TOP;
		} else if (HandleUtil.isInHorizontalTargetZone(x, y, left, right,
				bottom, targetRadius)) {
			pressedHandle = Handle.BOTTOM;
		} else if (HandleUtil.isInVerticalTargetZone(x, y, left, top, bottom,
				targetRadius)) {
			pressedHandle = Handle.LEFT;
		} else if (HandleUtil.isInVerticalTargetZone(x, y, right, top, bottom,
				targetRadius)) {
			pressedHandle = Handle.RIGHT;
		} else if (HandleUtil.isInCenterTargetZone(x, y, left, top, right,
				bottom) && !focusCenter()) {
			pressedHandle = Handle.CENTER;

		}

		return pressedHandle;
	}

	/**
	 * Calculates the offset of the touch point from the precise location of the
	 * specified handle.
	 * 
	 * @return the offset as a Pair where the x-offset is the first value and
	 *         the y-offset is the second value; null if the handle is null
	 */
	public static Pair<Float, Float> getOffset(Handle handle, float x, float y,
			float left, float top, float right, float bottom) {

		if (handle == null) {
			return null;
		}

		float touchOffsetX = 0;
		float touchOffsetY = 0;

		// Calculate the offset from the appropriate handle.
		switch (handle) {

		case TOP_LEFT:
			touchOffsetX = left - x;
			touchOffsetY = top - y;
			break;
		case TOP_RIGHT:
			touchOffsetX = right - x;
			touchOffsetY = top - y;
			break;
		case BOTTOM_LEFT:
			touchOffsetX = left - x;
			touchOffsetY = bottom - y;
			break;
		case BOTTOM_RIGHT:
			touchOffsetX = right - x;
			touchOffsetY = bottom - y;
			break;
		case LEFT:
			touchOffsetX = left - x;
			touchOffsetY = 0;
			break;
		case TOP:
			touchOffsetX = 0;
			touchOffsetY = top - y;
			break;
		case RIGHT:
			touchOffsetX = right - x;
			touchOffsetY = 0;
			break;
		case BOTTOM:
			touchOffsetX = 0;
			touchOffsetY = bottom - y;
			break;
		case CENTER:
			final float centerX = (right + left) / 2;
			final float centerY = (top + bottom) / 2;
			touchOffsetX = centerX - x;
			touchOffsetY = centerY - y;
			break;
		}

		final Pair<Float, Float> result = new Pair<Float, Float>(touchOffsetX,
				touchOffsetY);
		return result;
	}

	// Private Methods /////////////////////////////////////////////////////////

	/**
	 * Determines if the specified coordinate is in the target touch zone for a
	 * corner handle.
	 * 
	 * @param x
	 *            the x-coordinate of the touch point
	 * @param y
	 *            the y-coordinate of the touch point
	 * @param handleX
	 *            the x-coordinate of the corner handle
	 * @param handleY
	 *            the y-coordinate of the corner handle
	 * @param targetRadius
	 *            the target radius in pixels
	 * @return true if the touch point is in the target touch zone; false
	 *         otherwise
	 */
	private static boolean isInCornerTargetZone(float x, float y,
			float handleX, float handleY, float targetRadius) {

		if (Math.abs(x - handleX) <= targetRadius
				&& Math.abs(y - handleY) <= targetRadius) {
			return true;
		}
		return false;
	}

	/**
	 * Determines if the specified coordinate is in the target touch zone for a
	 * horizontal bar handle.
	 * 
	 * @param x
	 *            the x-coordinate of the touch point
	 * @param y
	 *            the y-coordinate of the touch point
	 * @param handleXStart
	 *            the left x-coordinate of the horizontal bar handle
	 * @param handleXEnd
	 *            the right x-coordinate of the horizontal bar handle
	 * @param handleY
	 *            the y-coordinate of the horizontal bar handle
	 * @param targetRadius
	 *            the target radius in pixels
	 * @return true if the touch point is in the target touch zone; false
	 *         otherwise
	 */
	private static boolean isInHorizontalTargetZone(float x, float y,
			float handleXStart, float handleXEnd, float handleY,
			float targetRadius) {

		if (x > handleXStart && x < handleXEnd
				&& Math.abs(y - handleY) <= targetRadius) {
			return true;
		}
		return false;
	}

	/**
	 * Determines if the specified coordinate is in the target touch zone for a
	 * vertical bar handle.
	 * 
	 * @param x
	 *            the x-coordinate of the touch point
	 * @param y
	 *            the y-coordinate of the touch point
	 * @param handleX
	 *            the x-coordinate of the vertical bar handle
	 * @param handleYStart
	 *            the top y-coordinate of the vertical bar handle
	 * @param handleYEnd
	 *            the bottom y-coordinate of the vertical bar handle
	 * @param targetRadius
	 *            the target radius in pixels
	 * @return true if the touch point is in the target touch zone; false
	 *         otherwise
	 */
	private static boolean isInVerticalTargetZone(float x, float y,
			float handleX, float handleYStart, float handleYEnd,
			float targetRadius) {

		if (Math.abs(x - handleX) <= targetRadius && y > handleYStart
				&& y < handleYEnd) {
			return true;
		}
		return false;
	}

	/**
	 * Determines if the specified coordinate falls anywhere inside the given
	 * bounds.
	 * 
	 * @param x
	 *            the x-coordinate of the touch point
	 * @param y
	 *            the y-coordinate of the touch point
	 * @param left
	 *            the x-coordinate of the left bound
	 * @param top
	 *            the y-coordinate of the top bound
	 * @param right
	 *            the x-coordinate of the right bound
	 * @param bottom
	 *            the y-coordinate of the bottom bound
	 * @return true if the touch point is inside the bounding rectangle; false
	 *         otherwise
	 */
	private static boolean isInCenterTargetZone(float x, float y, float left,
			float top, float right, float bottom) {

		if (x > left && x < right && y > top && y < bottom) {
			return true;
		}
		return false;
	}

	/**
	 * Determines if the cropper should focus on the center handle or the side
	 * handles. If it is a small image, focus on the center handle so the user
	 * can move it. If it is a large image, focus on the side handles so user
	 * can grab them. Corresponds to the appearance of the
	 * RuleOfThirdsGuidelines.
	 * 
	 * @return true if it is small enough such that it should focus on the
	 *         center; less than show_guidelines limit
	 */
	private static boolean focusCenter() {
		return (!CropOverlayView.showGuidelines());
	}
}
